#include "znx_openssl.h"
#include "comm/znx_slru.h"
#include "comm/znx_time.h"

#define ZNX_SSL_SESSION_CACHE_SLOTS     8

typedef struct {
    znx_spinlock_t                      locks[ZNX_SSL_SESSION_CACHE_SLOTS];
    znx_slru_t                          *slrus[ZNX_SSL_SESSION_CACHE_SLOTS];
} znx_ssl_session_cache_t;


static znx_ssl_session_cache_t          *g_ssl_session_cache;


static inline void
znx_ssl_session_id_free_handler(void *data)
{
    znx_ssl_session_id_destroy(data);
}


void
znx_ssl_session_cache_init(int64_t max_items)
{
    if (max_items <= 0) {
        return;
    }

    max_items /= ZNX_SSL_SESSION_CACHE_SLOTS;

    if (max_items < 16) {
        max_items = 16;
    }

    g_ssl_session_cache =
        znx_thread_calloc(sizeof(znx_ssl_session_cache_t));
    for (int i = 0; i < ZNX_SSL_SESSION_CACHE_SLOTS; i++) {
        znx_spinlock_init(&g_ssl_session_cache->locks[i]);
        g_ssl_session_cache->slrus[i] =
            znx_slru_create(2, max_items, 0, NULL,
                znx_ssl_session_id_free_handler,
                znx_ssl_session_id_cmp);
    }
}


void
znx_ssl_session_cache_put(znx_ssl_session_id_t *session_id)
{
    if (g_ssl_session_cache == NULL) {
        return;
    }

    int idx = session_id->crc % ZNX_SSL_SESSION_CACHE_SLOTS;
    znx_spinlock_lock(&g_ssl_session_cache->locks[idx]);
    znx_slru_put(g_ssl_session_cache->slrus[idx], session_id);
    znx_spinlock_unlock(&g_ssl_session_cache->locks[idx]);
}


SSL_SESSION *
znx_ssl_session_cache_get(znx_ssl_session_id_t *target)
{
    if (g_ssl_session_cache == NULL) {
        return NULL;
    }

    znx_ssl_session_id_t    *res = NULL;
    SSL_SESSION             *sess = NULL;
    u_char                  buf[ZNX_SSL_MAX_SESSION_SIZE];
    const u_char            *p;

    int idx = target->crc % ZNX_SSL_SESSION_CACHE_SLOTS;
    znx_spinlock_lock(&g_ssl_session_cache->locks[idx]);
    if (znx_slru_get(g_ssl_session_cache->slrus[idx], target, (void **)&res)) {
        if (res->expire_ms < znx_usec_2_msec(get_cache_time())) {
            znx_slru_remove(g_ssl_session_cache->slrus[idx], target);
            res = NULL;
        }
    }

    if (res != NULL) {
        znx_memcpy(buf, res->session, res->session_len);
        p = buf;
        sess = d2i_SSL_SESSION(NULL,
            &p, (long)res->session_len);
    }
    znx_spinlock_unlock(&g_ssl_session_cache->locks[idx]);

    return sess;
}


void
znx_ssl_session_cache_remove(znx_ssl_session_id_t *target)
{
    if (g_ssl_session_cache == NULL) {
        return;
    }

    int idx = target->crc % ZNX_SSL_SESSION_CACHE_SLOTS;
    znx_spinlock_lock(&g_ssl_session_cache->locks[idx]);
    znx_slru_remove(g_ssl_session_cache->slrus[idx], target);
    znx_spinlock_unlock(&g_ssl_session_cache->locks[idx]);
}


void
znx_ssl_session_cache_destroy()
{
    if (g_ssl_session_cache == NULL) {
        return;
    }

    for (int i = 0; i < ZNX_SSL_SESSION_CACHE_SLOTS; i++) {
        znx_slru_destroy(g_ssl_session_cache->slrus[i]);
    }

    znx_thread_free(g_ssl_session_cache);
}